package com.xhj.mybatis.mapping;

import com.xhj.mybatis.annotations.Param;
import com.xhj.mybatis.reflection.ParamNameUtil;
import com.xhj.mybatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.*;

/**
 * ResultMap
 *
 * @author XJks
 * @description ResultMap 类用于描述数据库查询结果与 Java 对象属性之间的映射关系。TODO:作用与 {@link BoundSql} 类似。
 */
public class ResultMap {
    /**
     * MyBatis 全局配置对象
     */
    private Configuration configuration;

    /**
     * 当前 ResultMap 的唯一标识
     */
    private String id;

    /**
     * 映射的 Java 类型。TODO:对标 {@link BoundSql#parameterObject}。{@link BoundSql#parameterObject} 表示的是 XxxMapper.class 对应方法的整个方法参数列表的包装，是动态数值，没有类型信息。{@link type} 表示的是最终映射结果的类型，有类型信息。
     */
    private Class<?> type;

    /**
     * 所有的映射关系。TODO:对标 {@link BoundSql#parameterMappings}。
     */
    private List<ResultMapping> resultMappings;

    /**
     * XxxMapper.xml 中 {@code <ResultMap>} 标签中的 {@code <id>}标签配置的主键映射关系
     */
    private List<ResultMapping> idResultMappings;

    /**
     * XxxMapper.xml 中 {@code <ResultMap>} 标签中的 {@code <constructor>}标签配置的构造方法参数映射关系
     */
    private List<ResultMapping> constructorResultMappings;

    /**
     * XxxMapper.xml 中 {@code <ResultMap>} 标签中的 {@code <result>}标签配置的普通属性映射关系
     */
    private List<ResultMapping> propertyResultMappings;
    /**
     * 映射的数据库列名集合
     */
    private Set<String> mappedColumns;

    /**
     * 映射的 Java 属性名集合
     */
    private Set<String> mappedProperties;

    /**
     * 是否自动映射
     */
    private Boolean autoMapping;

    /**
     * 私有构造方法，防止外部直接实例化
     */
    private ResultMap() {
    }

    /**
     * ResultMap 建造者类
     */
    public static class Builder {

        /**
         * 日志对象
         */
        private static final Logger log = LoggerFactory.getLogger(Builder.class);

        /**
         * 正在构建的 ResultMap 实例
         */
        private ResultMap resultMap = new ResultMap();

        /**
         * 构造方法，初始化 ResultMap 的基本属性
         *
         * @param configuration  MyBatis 全局配置对象
         * @param id             ResultMap 的唯一标识
         * @param type           映射的 Java 类型
         * @param resultMappings 所有的映射关系
         */
        public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
            this(configuration, id, type, resultMappings, null);
        }

        /**
         * 构造方法，初始化 ResultMap 的基本属性，包括自动映射选项
         *
         * @param configuration  MyBatis 全局配置对象
         * @param id             ResultMap 的唯一标识
         * @param type           映射的 Java 类型
         * @param resultMappings 所有的映射关系
         * @param autoMapping    是否启用自动映射
         */
        public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings, Boolean autoMapping) {
            resultMap.configuration = configuration;
            resultMap.id = id;
            resultMap.type = type;
            resultMap.resultMappings = resultMappings;
            resultMap.autoMapping = autoMapping;
        }

        // 获取映射的 Java 类型
        public Class<?> type() {
            return resultMap.type;
        }

        // 构建 ResultMap 实例
        public ResultMap build() {
            // id 不能为空
            if (resultMap.id == null) {
                throw new IllegalArgumentException("ResultMaps must have an id");
            }
            // 初始化集合
            resultMap.mappedColumns = new HashSet<>();
            resultMap.mappedProperties = new HashSet<>();
            resultMap.idResultMappings = new ArrayList<>();
            resultMap.constructorResultMappings = new ArrayList<>();
            resultMap.propertyResultMappings = new ArrayList<>();
            final List<String> constructorArgNames = new ArrayList<>();
            // 遍历所有映射关系
            for (ResultMapping resultMapping : resultMap.resultMappings) {
                // 处理列名
                final String column = resultMapping.getColumn();
                if (column != null) {
                    resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
                }
                // 处理属性名
                final String property = resultMapping.getProperty();
                if (property != null) {
                    resultMap.mappedProperties.add(property);
                }
                // 判断是否为构造方法参数
                if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
                    resultMap.constructorResultMappings.add(resultMapping);
                    if (resultMapping.getProperty() != null) {
                        constructorArgNames.add(resultMapping.getProperty());
                    }
                } else {
                    // 普通属性映射
                    resultMap.propertyResultMappings.add(resultMapping);
                }
                // 判断是否为主键
                if (resultMapping.getFlags().contains(ResultFlag.ID)) {
                    resultMap.idResultMappings.add(resultMapping);
                }
            }
            // 如果没有主键映射，则全部作为主键
            if (resultMap.idResultMappings.isEmpty()) {
                resultMap.idResultMappings.addAll(resultMap.resultMappings);
            }
            // 如果有构造方法参数，需排序
            if (!constructorArgNames.isEmpty()) {
                final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
                if (actualArgNames == null) {
                    throw new RuntimeException("Error in result map '" + resultMap.id
                            + "'. Failed to find a constructor in '"
                            + resultMap.getType().getName() + "' by arg names " + constructorArgNames
                            + ". There might be more info in debug log.");
                }
                // 按构造方法参数顺序排序
                resultMap.constructorResultMappings.sort((o1, o2) -> {
                    int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
                    int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
                    return paramIdx1 - paramIdx2;
                });
            }
            // 将集合变为不可变，防止外部修改
            resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
            resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
            resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
            resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
            resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
            return resultMap;
        }

        /**
         * 查找与参数名匹配的构造方法
         *
         * @param constructorArgNames 构造方法参数名列表
         * @return 匹配的参数名列表，若无匹配则返回 null
         */
        private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
            // 获取当前映射类型（type）的所有构造方法
            Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
            // 遍历每一个构造方法
            for (Constructor<?> constructor : constructors) {
                // 获取该构造方法的所有参数类型
                Class<?>[] paramTypes = constructor.getParameterTypes();
                // 如果参数数量和传入的参数名数量一致
                if (constructorArgNames.size() == paramTypes.length) {
                    // 获取该构造方法的参数名列表
                    List<String> paramNames = getArgNames(constructor);
                    // 判断：传入的参数名列表是否包含所有构造方法参数名，并且参数类型也匹配
                    if (constructorArgNames.containsAll(paramNames)
                            && argTypesMatch(constructorArgNames, paramTypes, paramNames)) {
                        // 如果匹配，返回这个构造方法的参数名列表
                        return paramNames;
                    }
                }
            }
            // 如果没有找到匹配的构造方法，返回 null
            return null;
        }

        /**
         * 检查参数类型是否匹配
         *
         * @param constructorArgNames 构造方法参数名列表
         * @param paramTypes          构造方法参数类型数组
         * @param paramNames          构造方法参数名列表
         * @return 类型是否匹配
         */
        private boolean argTypesMatch(final List<String> constructorArgNames,
                                      Class<?>[] paramTypes, List<String> paramNames) {
            // 遍历每一个构造方法参数名
            for (int i = 0; i < constructorArgNames.size(); i++) {
                // 获取当前参数名在参数名列表中的索引，然后用这个索引从参数类型数组中取出实际类型
                Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
                // 获取在 ResultMap 中指定的该参数的 Java 类型
                Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();
                // 如果实际类型和指定类型不一致
                if (!actualType.equals(specifiedType)) {
                    // 如果日志级别为 debug，则输出详细的类型不匹配信息
                    if (log.isDebugEnabled()) {
                        log.debug("While building result map '" + resultMap.id
                                + "', found a constructor with arg names " + constructorArgNames
                                + ", but the type of '" + constructorArgNames.get(i)
                                + "' did not match. Specified: [" + specifiedType.getName() + "] Declared: ["
                                + actualType.getName() + "]");
                    }
                    // 类型不匹配，返回 false
                    return false;
                }
            }
            // 所有参数类型都匹配，返回 true
            return true;
        }

        /**
         * 获取构造方法的参数名列表。
         * 优先使用 @Param 注解的值，如果没有注解则尝试获取实际参数名，最后用 arg+索引 作为参数名。
         *
         * @param constructor 构造方法对象
         * @return 参数名列表
         */
        private List<String> getArgNames(Constructor<?> constructor) {
            // 用于存储最终的参数名
            List<String> paramNames = new ArrayList<>();
            // 用于缓存实际参数名（如果需要获取）
            List<String> actualParamNames = null;
            // 获取每个参数上的注解数组
            final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
            // 参数个数
            int paramCount = paramAnnotations.length;
            // 遍历每一个参数
            for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
                // 当前参数名，初始为 null
                String name = null;
                // 遍历当前参数的所有注解
                for (Annotation annotation : paramAnnotations[paramIndex]) {
                    // 如果有 @Param 注解，则用注解的值作为参数名
                    if (annotation instanceof Param) {
                        name = ((Param) annotation).value();
                        break; // 找到后就不用再查找其他注解
                    }
                }
                // 如果没有 @Param 注解，并且允许使用实际参数名
                if (name == null && resultMap.configuration.isUseActualParamName()) {
                    // 只获取一次实际参数名列表
                    if (actualParamNames == null) {
                        actualParamNames = ParamNameUtil.getParamNames(constructor);
                    }
                    // 如果实际参数名列表足够长，则取出对应的参数名
                    if (actualParamNames.size() > paramIndex) {
                        name = actualParamNames.get(paramIndex);
                    }
                }
                // 如果还没有参数名，则用 arg+索引 作为参数名（如 arg0, arg1）
                paramNames.add(name != null ? name : "arg" + paramIndex);
            }
            // 返回参数名列表
            return paramNames;
        }

    }

    /**
     * 获取 ResultMap 的 id
     *
     * @return ResultMap 的唯一标识
     */
    public String getId() {
        return id;
    }

    /**
     * 获取映射的 Java 类型
     *
     * @return 映射的 Java 类型
     */
    public Class<?> getType() {
        return type;
    }

    /**
     * 获取所有映射关系
     *
     * @return 映射关系列表
     */
    public List<ResultMapping> getResultMappings() {
        return resultMappings;
    }

    /**
     * 获取构造方法参数的映射关系
     *
     * @return 构造方法参数映射关系列表
     */
    public List<ResultMapping> getConstructorResultMappings() {
        return constructorResultMappings;
    }

    /**
     * 获取普通属性的映射关系
     *
     * @return 普通属性映射关系列表
     */
    public List<ResultMapping> getPropertyResultMappings() {
        return propertyResultMappings;
    }

    /**
     * 获取主键的映射关系
     *
     * @return 主键映射关系列表
     */
    public List<ResultMapping> getIdResultMappings() {
        return idResultMappings;
    }

    /**
     * 获取映射的数据库列名集合
     *
     * @return 映射的数据库列名集合
     */
    public Set<String> getMappedColumns() {
        return mappedColumns;
    }

    /**
     * 获取映射的 Java 属性名集合
     *
     * @return 映射的 Java 属性名集合
     */
    public Set<String> getMappedProperties() {
        return mappedProperties;
    }

    /**
     * 获取自动映射配置
     *
     * @return 是否启用自动映射
     */
    public Boolean getAutoMapping() {
        return autoMapping;
    }

}
